import { IAppDEUIAction } from '@ibiz/model-core'
import {
  IUIActionResult,
  IUILogicParams,
  FrontUIActionProvider,
  CodeListItem
} from '@ibiz-template/runtime'
import { RuntimeModelError } from '@ibiz-template/core'
import ExcelJS from 'exceljs';

/**
 * 数据导入行为适配器
 *
 * @export
 * @class DataImportUIActionProvider
 * @extends {FrontUIActionProvider}
 */
export class DataImportUIActionProvider extends FrontUIActionProvider {
  /**
   *上下文
   *
   * @private
   * @type {IContext}
   * @memberof DataImportUIActionProvider
   */
  private context!: IContext

  /**
   * 视图参数
   *
   * @private
   * @type {IParams}
   * @memberof DataImportUIActionProvider
   */
  private params!: IParams

  /**
   * 代码表Map
   *
   * @private
   * @type {Map<string, CodeListItem[]>}
   * @memberof DataImportUIActionProvider
   */
  private codeListMap: Map<string, readonly CodeListItem[]> = new Map()

  async execAction(action: IAppDEUIAction, args: IUILogicParams): Promise<IUIActionResult> {
    const actionResult: IUIActionResult = {
      cancel: true
    }
    this.context = args.context
    this.params = args.params
    if (action.navigateParams) {
      let model: IData | null = null
      try {
        action.navigateParams.forEach((params) => {
          if (params.key === 'dataimportmodel' && params.value) {
            model = JSON.parse(params.value) as IData
          }
        })
      } catch (error) {
        throw new RuntimeModelError(action, 'dataimportmodel 配置错误')
      }
      if (model) {
        const data = await this.dataImport(model)
        if (data && Object.keys(data).length > 0) {
          Object.assign(actionResult, {
            cancel: false,
            data: [data]
          })
        }
      } else {
        throw new RuntimeModelError(action, '未配置 dataimportmodel')
      }
    }
    return actionResult
  }

  /**
   * 数据导入
   *
   * @private
   * @param {IData} model 导入模型
   * @memberof DataImportUIActionProvider
   */
  private async dataImport(model: IData): Promise<IData | null> {
    const file = await this.getFile()
    if (file) {
      const readFile = (_file: File) => {
        return new Promise((resolve) => {
          const reader = new FileReader()
          reader.readAsBinaryString(_file)
          reader.onload = (ev) => {
            resolve(ev.target?.result)
          }
        })
      }
      const buffer = await readFile(file)
      const workbook = new ExcelJS.Workbook()
      await workbook.xlsx.load(buffer as any)
      const worksheet = this.handleImportData(workbook)
      const data: IData = {}
      for (const sheetName in worksheet) {
        if (Object.prototype.hasOwnProperty.call(worksheet, sheetName)) {
          const items = worksheet[sheetName]
          const sheetModel: IData = {}
          Object.keys(model).forEach((key) => {
            if (model[key].caption === sheetName) {
              Object.assign(sheetModel, {
                codeName: key,
                ...model[key]
              })
            }
          })
          if (sheetModel.codeName) {
            await this.initCodeListMap(sheetModel)
            data[sheetModel.codeName] = this.transformedData(sheetModel, items)
          }
        }
      }
      return data
    }
    return null
  }

  /**
   * 处理导入数据
   *
   * @private
   * @param {IData} workbook 工作簿
   * @memberof DataImportUIActionProvider
   */
  private handleImportData(workbook: ExcelJS.Workbook) {
    const data: IData = {}
    workbook.eachSheet((worksheet) => {
      const rows: any[][] = []
      worksheet.eachRow((row) => {
        const values: any = row.values
        rows.push(values.slice(1, values.length))
      })
      if (rows.length > 1) {
        const header: string[] = rows[0]
        const rowData: any[][] = rows.slice(1, rows.length)
        const items: IData[] = []
        rowData.forEach((row) => {
          const item: IData = {}
          row.forEach((col: any, index: number) => {
            item[header[index]] = col && col !== 0 ? col : null
          })
          items.push(item)
        })
        Object.assign(data, {
          [worksheet.name]: items
        })
      }
    })
    return data
  }

  /**
   * 获取导入文件
   *
   * @private
   * @return {*}  {(Promise<File | null>)}
   * @memberof DataImportUIActionProvider
   */
  private getFile(): Promise<File | null> {
    return new Promise((resolve) => {
      const fileInput = document.createElement('input') as HTMLInputElement
      fileInput.type = 'file'
      fileInput.style.display = 'none'
      fileInput.accept = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
      fileInput.addEventListener('change', () => {
        const fileList = fileInput.files
        if (fileList && fileList.length > 0) {
          resolve(fileList[0])
        } else {
          resolve(null)
        }
      })
      fileInput.addEventListener('cancel', () => {
        resolve(null)
      })
      fileInput.click()
    })
  }

  /**
   * 转化数据
   *
   * @private
   * @param {IData} sheetModel 工作表模型
   * @param {IData[]} data 工作表数据
   * @return {*}  {IData[]}
   * @memberof DataImportUIActionProvider
   */
  private transformedData(sheetModel: IData, data: IData[]): IData[] {
    const items: IData[] = []
    const columnNames: string[] = sheetModel.column ? Object.keys(sheetModel.column) : []
    if (columnNames.length > 0) {
      data.forEach((item) => {
        const _item: IData = {}
        columnNames.forEach((columnName) => {
          const codeList = this.codeListMap.get(`${sheetModel.codeName}_${columnName}`)
          const value = item[sheetModel.column[columnName]]
          if (codeList) {
            const codeListItem = codeList.find((code) => Object.is(code.text, value))
            if (codeListItem) {
              _item[columnName] = codeListItem.value
            }
          } else {
            _item[columnName] = value
          }
        })
        items.push(_item)
      })
    }
    return items
  }

  /**
   * 初始化代码表Map
   *
   * @param {IData} sheetModel
   * @memberof DataImportUIActionProvider
   */
  private async initCodeListMap(sheetModel: IData) {
    const columnNames: string[] = sheetModel.codeList ? Object.keys(sheetModel.codeList) : []
    if (columnNames.length > 0) {
      for (const columnName of columnNames) {
        const codeList = this.codeListMap.get(`${sheetModel.codeName}_${columnName}`)
        if (!codeList) {
          const codeListItems = await this.loadCodeList(sheetModel.codeList[columnName])
          this.codeListMap.set(`${sheetModel.codeName}_${columnName}`, codeListItems)
        }
      }
    }
  }

  /**
   * 加载代码表数据
   *
   * @param {string} appCodeListId 代码表标识
   * @return {*}  {Promise<readonly}
   * @memberof DataImportUIActionProvider
   */
  private async loadCodeList(appCodeListId: string): Promise<readonly CodeListItem[]> {
    const app = await ibiz.hub.getApp(this.context.srfappid)
    let dataItems: readonly CodeListItem[] = []
    dataItems = await app.codeList.get(appCodeListId, this.context, this.params)
    return dataItems
  }
}
